﻿using System;
using System.Linq;
using System.Text;

namespace cludg.Services.Security
{
	using Domain.Users;
	using Images;
	using Repository.Security;
	using Repository.Transactions;
	using Infrastructure.Core.Logging;
	using Infrastructure.Core.Attributes;

	public class Administration : IAdministration
	{
		protected readonly IImageService ImageService;
		protected readonly ISecurityRepository Repository;
		protected readonly ITransactionFactory TransactionFactory;

		public Administration(
			ISecurityRepository repository, 
			IImageService imageService,
			ITransactionFactory transactionFactory)
		{
			Repository = repository;
			ImageService = imageService;
			TransactionFactory = transactionFactory;
		}

		private ILogger _logger = new NullLogger();
		[Dependency]
		public ILogger Logger { get { return _logger; } set { _logger = value; } }

		public void ActivateUser(string userId)
		{
			UserSecurityInfo info = Repository.GetUserSecurityInfo(userId);

			if ((info != null) && (info.State == UserSecurityInfo.UserState.NeedActivation))
			{
				using (var tx = TransactionFactory.BeginTransaction())
				{
					info.State = UserSecurityInfo.UserState.Active;
					Repository.ChangeUserSecurityInfo(userId, info);
					tx.Commit();
				}
			}
		}

		#region Register
		protected string PrepareUniqueName(string name)
		{
			var builder = new StringBuilder(name.Length);
			foreach (var c in name.Where(Char.IsLetterOrDigit))
				builder.Append(c);
			return builder.ToString();
		}

		protected string GenerateUniqueName(string baseName)
		{
			baseName = PrepareUniqueName(baseName);
			if (String.IsNullOrWhiteSpace(baseName))
				baseName = PrepareUniqueName(DateTime.Now.ToString());
			var name = baseName;
			var index = new Random().Next(1, 100);
			while (Repository.AllUsers.Any(x => x.UniqueName == name))
			{
				index++;
				name = String.Format("{0}{1}", baseName, index);
			}
			return name;
		}

		public string RegisterUser(User user, string name, string password, bool needActivation = true)
		{
			if (Repository.HasUserSecurityInfoByEmail(user.Email))
				throw new UserAlreadyExistsError(user, String.Format("Почтовый ящик {0} уже зарегистрирован", user.Email));

			if (Repository.HasUserSecurityInfoByName(name))
				throw new UserAlreadyExistsError(user, String.Format("Пользователь с именем {0} уже зарегистрирован", name));

			string userId = null;

			user.CreationDate = DateTime.Now;

			using (var tx = TransactionFactory.BeginTransaction())
			{
				user.UniqueName = GenerateUniqueName(user.DisplayName);
				userId = Repository.AddUser(user);
				tx.Commit();
			}

			using (var tx = TransactionFactory.BeginTransaction())
			{
				UserSecurityInfo info = UserSecurityInfo.CreateForUser(user);
				info.LoginType = UserSecurityInfo.UserLoginType.LoginPassword;
				info.Name = name;
				info.PasswordHash = Utils.PasswordHash.Get(password);
				info.State = needActivation ? UserSecurityInfo.UserState.NeedActivation : UserSecurityInfo.UserState.Active;
				Repository.ChangeUserSecurityInfo(userId, info);

				tx.Commit();
			}

			Logger.InfoFormat("User {0} (id={1}) registered", user.DisplayName, user.Id);

			return userId;
		}

		public string RegisterThirdPartyUser(User user, string thirdPartyIdent)
		{
			if (Repository.HasUserSecurityInfoByThirdPartyIdent(thirdPartyIdent))
				throw new UserAlreadyExistsError(user, String.Format("Пользователь с id {0} уже зарегистрирован", thirdPartyIdent));

			string userId = null;

			user.CreationDate = DateTime.Now;

			using (var tx = TransactionFactory.BeginTransaction())
			{
				user.UniqueName = GenerateUniqueName(user.DisplayName);
				userId = Repository.AddUser(user);
				tx.Commit();
			}

			using (var tx = TransactionFactory.BeginTransaction())
			{
				UserSecurityInfo info = UserSecurityInfo.CreateForUser(user);
				info.LoginType = UserSecurityInfo.UserLoginType.ThirdParty;
				info.ThirdPartyIdent = thirdPartyIdent;
				info.State = UserSecurityInfo.UserState.Active;
				Repository.ChangeUserSecurityInfo(userId, info);

				tx.Commit();
			}

			return userId;
		} 
		#endregion

		private void DoResetUserPassword(UserSecurityInfo info, out string newPassword)
		{
			newPassword = string.Empty;
			if ((info != null) && (info.LoginType == UserSecurityInfo.UserLoginType.LoginPassword) && (info.State == UserSecurityInfo.UserState.Active))
			{
				using (var tx = TransactionFactory.BeginTransaction())
				{
					newPassword = Utils.Password.Generate();
					info.PasswordHash = Utils.PasswordHash.Get(newPassword);
					Repository.ChangeUserSecurityInfo(info.Id, info);

					tx.Commit();
				}
			}
		}

		private void DoSetUserPassword(UserSecurityInfo info, string newPassword)
		{
			if ((info != null) && (info.LoginType == UserSecurityInfo.UserLoginType.LoginPassword) && (info.State == UserSecurityInfo.UserState.Active))
			{
				using (var tx = TransactionFactory.BeginTransaction())
				{
					info.PasswordHash = Utils.PasswordHash.Get(newPassword);
					Repository.ChangeUserSecurityInfo(info.Id, info);

					tx.Commit();
				}
			}
		}

		public void ResetUserPassword(string userId, out string newPassword)
		{
			DoResetUserPassword(Repository.GetUserSecurityInfo(userId), out newPassword);
		}

		public void SetUserPassword(string userId, string newPassword)
		{
			DoSetUserPassword(Repository.GetUserSecurityInfo(userId), newPassword);
		}

		public void ResetUserPasswordByEmail(string email, out string newPassword)
		{
			DoResetUserPassword(Repository.GetUserSecurityInfoByEmail(email), out newPassword);
		}
	}
}
